#pragma once

#include <iostream>
#include <unistd.h>
#include <memory>
#include "util.hpp"
#include <fstream>
#include <cassert>
#include <time.h>



namespace my_log
{

    template<class SinkType>
    class SinkFactory;

    class StdOutSink;
    class FileSink;
    class RollSink;

    class Sink
    {
    public:
        using SinkPtr = std::shared_ptr<Sink>;
        virtual ~Sink(){}
        virtual void log(void *data, size_t len){}
    };

    class StdOutSink : public Sink
    {
    public:
        void log(void *data, size_t len)
        {
            cout.write((char*)data, len);
        }
    };



    class FileSink : public Sink
    {
    public:
        FileSink(std::string filename)
            :_filename(filename)
        {
            my_log::Util::File::CreateDirectory(my_log::Util::File::GetPath(filename));
            _ofs.open(filename, std::ios::binary | std::ios::app);

            assert(_ofs.is_open());
        }

        void log(void *data, size_t len)
        {
            _ofs.write((char*)data, len);
        }

    private:
        std::string _filename;
        std::fstream _ofs;
    };

    class RollSink : public Sink
    {
    public:
        RollSink(std::string basedir, size_t max)
            :_basedir(basedir),
            _max_index(max)
        {
            std::string filename;
            filename = CreateNewFile();
            my_log::Util::File::CreateDirectory(my_log::Util::File::GetPath(filename));
            _ofs.open(filename, std::ios::binary | std::ios::app);
            assert(_ofs.is_open());
        }

        void log(void *data, size_t len)
        {
            if (_cur_index + len > _max_index)
            {
                std::string filename = CreateNewFile();
                my_log::Util::File::CreateDirectory(my_log::Util::File::GetPath(filename));

                _ofs.close();
                _ofs.open(filename, std::ios::binary | std::ios::app);
                assert(_ofs.is_open());
                _cur_index = 0;
            }

            _ofs.write((char*)data, len);
            _cur_index += len;
        }
        
    
    private:
        std::string CreateNewFile()
        {
            stringstream ss;
            std::string _filename;
            _filename = _basedir;

            time_t curt = time(nullptr);
            struct tm recurt;
            localtime_r(&curt, &recurt);

            ss << _filename;
            ss << 1900 + recurt.tm_year;
            ss << 1 + recurt.tm_mon;
            ss << recurt.tm_mday;
            ss << recurt.tm_hour;
            ss << recurt.tm_min;
            ss << recurt.tm_sec;
            ss << "-" << _count++;
            ss << ".log";
            return ss.str();
        }
    
    private:
        std::string _basedir;
        std::fstream _ofs;
        size_t _cur_index;
        size_t _max_index;
        size_t _count;
    };

    enum class TimeGap
    {
        GAPSECOND,
        GAPMIN,
        GAPHOUR,
        GAPDAY
    };

    class RollByTimeSink
    {
    public:
        RollByTimeSink(std::string filename, my_log::TimeGap gap)
            :_basedir(filename)
        {
            switch(gap)
            {
                case my_log::TimeGap::GAPSECOND: 
                    _gap_size = 1;
                    break;
                case my_log::TimeGap::GAPMIN:
                    _gap_size = 60;
                    break;
                case my_log::TimeGap::GAPHOUR:
                    _gap_size = 60 * 60;
                    break;
                case my_log::TimeGap::GAPDAY:
                    _gap_size = 3600 * 24;
                    break;
                default:
                    std::cout << "please input right gap" << std::endl;                
            }

            time_t curtime = time(nullptr);
            _cur_gap = (_gap_size == 1) ? curtime : curtime % _gap_size;

            std::string _filename = CreateNewFile(); 
            my_log::Util::File::CreateDirectory(my_log::Util::File::GetPath(_filename));
            _ofs.open(_filename, std::ios::binary | std::ios::app);

            assert(_ofs.is_open());
        }

        void log(void *data, size_t len)
        {
            time_t curtime = time(nullptr);

            size_t newcur_gap = (_gap_size == 1) ? _gap_size : curtime % _gap_size;
            if (newcur_gap != _cur_gap)
            {
                std::string filename = CreateNewFile();
                my_log::Util::File::CreateDirectory(my_log::Util::File::GetPath(filename));
                _ofs.close();
                _ofs.open(filename, std::ios::binary | std::ios::app);

                assert(_ofs.is_open());
                _cur_gap = newcur_gap;
            }

            _ofs.write((char*)data, len);   
        }

    private:
    std::string CreateNewFile()
    {
        stringstream ss;
        std::string _filename;
        _filename = _basedir;

        time_t curt = time(nullptr);
        struct tm recurt;
        localtime_r(&curt, &recurt);

        ss << _filename;
        ss << 1900 + recurt.tm_year;
        ss << 1 + recurt.tm_mon;
        ss << recurt.tm_mday;
        ss << recurt.tm_hour;
        ss << recurt.tm_min;
        ss << recurt.tm_sec;
        ss << "-" << _cur_gap;
        ss << ".log";
        return ss.str();
    }

    public:
        std::string _basedir;
        std::fstream _ofs;

        size_t _cur_gap;
        size_t _gap_size;
    };

    
    template<class SinkType>
    class SinkFactory
    {
    public:
        template<class ...Args>
        static Sink::SinkPtr create(Args &&...args)
        {
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };

    struct SinkTest
    {
        static void Test()
        {
            my_log::Sink::SinkPtr ptr1 = my_log::SinkFactory<StdOutSink>::create();
            my_log::Sink::SinkPtr ptr2 = my_log::SinkFactory<FileSink>::create("./Log/sigfile"); 
            my_log::Sink::SinkPtr ptr3 = my_log::SinkFactory<RollSink>::create("./Log/rollfile-", 10000);
            
            my_log::Log_Msg msg(__FILE__, __LINE__, "root_logger", my_log::LogLevel::level::DEBUG, ":");
            Formatter fmt;
            std::string ret = fmt.format(msg);
            ret.pop_back();
            int cnt = 0;
            while (cnt < 50 * 10000)
            {
                cnt++;
    
                std::string tmp = ret + to_string(cnt);
                tmp += '\n'; 
                ptr1->log((void*)tmp.c_str(), tmp.size());
                ptr2->log((void*)tmp.c_str(), tmp.size());
                ptr3->log((void*)tmp.c_str(), tmp.size());
            }
        }
    };


}